package com.xpc.easyes.autoconfig.register;

import com.xpc.easyes.core.anno.TableField;
import com.xpc.easyes.core.anno.TableName;
import com.xpc.easyes.core.cache.BaseCache;
import com.xpc.easyes.core.common.IndexInit;
import com.xpc.easyes.core.conditions.LambdaEsIndexWrapper;
import com.xpc.easyes.core.enums.Analyzer;
import com.xpc.easyes.core.enums.FieldType;
import com.xpc.easyes.core.enums.IndexStrategy;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 扫描指定路径下的所有接口
 * <p>
 * Copyright © 2021 xpc1024 All Rights Reserved
 **/
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<>();

    public void setMapperFactoryBean(MapperFactoryBean<?> mapperFactoryBean) {
        this.mapperFactoryBean = mapperFactoryBean != null ? mapperFactoryBean : new MapperFactoryBean<>();
    }

    public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }

    public void registerFilters() {
        // default include filter that accepts all classes
        addIncludeFilter((metadataReader, metadataReaderFactory) -> true);

        // exclude package-info.java
        addExcludeFilter((metadataReader, metadataReaderFactory) -> {
            String className = metadataReader.getClassMetadata().getClassName();
            return className.endsWith("package-info");
        });
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            logger.warn("No Easy-Es mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            processBeanDefinitions(beanDefinitions);
        }
        return beanDefinitions;
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
                    + "' and '" + beanClassName + "' mapperInterface");

            setLambdaEsIndexWrapper(beanClassName);
            // the mapper interface is the original class of the bean
            // but, the actual class of the bean is MapperFactoryBean
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            definition.setBeanClass(this.mapperFactoryBean.getClass());

            logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }

    }


    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
        if (super.checkCandidate(beanName, beanDefinition)) {
            return true;
        } else {
            logger.warn("Skipping MapperFactoryBean with name '" + beanName
                    + "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface"
                    + ". Bean already defined with the same name!");
            return false;
        }
    }


    /**
     * 设置es包装器
     *
     * @param beanClassName bean类名称
     */
    private void setLambdaEsIndexWrapper(String beanClassName) {
        IndexInit indexInit = new IndexInit();
        Class<?> entityClass = null;
        Class<?> mapperClass = null;
        try {
             mapperClass = Class.forName(beanClassName);
            Type type = ((ParameterizedType) mapperClass.getGenericInterfaces()[0]).getActualTypeArguments()[0];
            entityClass = (Class<?>) type;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            logger.debug("bean class get error:" + e.getMessage());
        }
        indexInit.setMapperClass(mapperClass);
        indexInit.setEntityClass(entityClass);
        LambdaEsIndexWrapper<?> wrapper = new LambdaEsIndexWrapper<>();
        /**
         * 索引策略
         */
        TableName tableName = entityClass.getAnnotation(TableName.class);
        IndexStrategy indexStrategy = Optional.ofNullable(tableName).map(TableName::indexStrategy).orElse(IndexStrategy.UPDATE);
        String indexName = entityClass.getSimpleName().toLowerCase();
        wrapper.indexName(indexName);
        wrapper.createAlias(Optional.ofNullable(tableName).map(TableName::aliasName).orElse(null));
        wrapper.createReplicasNum(Optional.ofNullable(tableName).map(TableName::replicasNum).orElse(null));
        wrapper.createShardsNum(Optional.ofNullable(tableName).map(TableName::shardsNum).orElse(null));
        Field[] fields = entityClass.getDeclaredFields();
        for (Field field : fields) {
            TableField annotation = field.getAnnotation(TableField.class);
            FieldType fieldType = annotation.fieldType();
            Analyzer analyzer = annotation.analyzer();
            Analyzer searchAnalyzer = annotation.searchAnalyzer();
            wrapper.mapping(field.getName(), fieldType, analyzer, searchAnalyzer);

        }
        indexInit.setIndexStrategy(indexStrategy);
        indexInit.setWrapper(wrapper);
        BaseCache.INDEX_INITS.add(indexInit);
    }
}
